package individual.ircon.bilibili.network;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static individual.ircon.bilibili.network.DanmuCoder.frameEncoder;
import static individual.ircon.bilibili.network.DanmuHttpApi.getHandshakeInfo;

/**
 * 用于链接bilibili弹幕服务器。
 */
public final class DanmuWSSClient extends WebSocketClient {
    /**
     * 心跳信息格式固定。
     */
    private static final byte[] HEAT_BEAT_DATA = frameEncoder(
            0x0000_001f, (short) 0x0010, (short) 0x0001, 0x0000_0002, 0x0000_0001,
            new byte[]{
                    0x5b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20,
                    0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5d
            }
    );
    /**
     * 用于周期性的发送心跳。
     */
    private ScheduledExecutorService ExecutorService = null;

    /**
     * 出现错误时，回调。
     */
    private final OnError OnE;

    /**
     * 关闭时，回调。
     */
    private final OnClose OnC;

    /**
     * 打开时，回调。
     */
    private final OnOpen OnO;

    /**
     * 接收到字节消息时回调。
     */
    private final OnMessageByte OmsB;

    /**
     * 接收到字符消息时候回调。
     */
    private final OnMessageString OmsS;

    private DanmuWSSClient(Builder builder, URI serverUri) {
        super(serverUri);
        this.OmsB = builder.OmsB;
        this.OmsS = builder.OmsS;
        this.OnC = builder.OnC;
        this.OnE = builder.OnE;
        this.OnO = builder.OnO;
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        if(OnO != null)
            OnO.onOpen(handshakedata);
    }

    @Override
    public void onMessage(String message) {
        if(OmsS != null)
            OmsS.onMessage(message);

    }

    @Override
    public void onMessage(ByteBuffer bytes) {
        if(OmsB != null)
            OmsB.onMessage(bytes);
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        if(OnC != null)
            OnC.onClose(code, reason, remote);
    }

    @Override
    public void onError(Exception ex) {
        if(OnE != null)
            OnE.onError(ex);
    }

    /// === 工具接口 ===
    public interface OnOpen {
        void onOpen(ServerHandshake handshake);
    }
    public interface OnClose {
        void onClose(int code, String reason, boolean remote);
    }
    public interface OnError {
        void onError(Exception e);
    }
    public interface OnMessageString {
        void onMessage(String message);
    }
    public interface OnMessageByte {
        void onMessage(ByteBuffer bytes);
    }

    /// === === === === === ===

    /**
     * 启动客户端工作。
     * @param handshake_data 握手信息
     * @param isSendHeartBeat 是否定时发送心跳
     * @throws InterruptedException 如果出现链接中断
     */
    private void start(byte[] handshake_data, boolean isSendHeartBeat) throws InterruptedException {
        this.connectBlocking();

        // 发送握手信息
        this.send(handshake_data);

        // 启动心跳
        if(isSendHeartBeat) {
            this.ExecutorService = Executors.newSingleThreadScheduledExecutor();
            this.ExecutorService.scheduleAtFixedRate(
                    ()-> {
                        if (this.isOpen())
                            this.send(HEAT_BEAT_DATA);
                    },
                    10L,
                    20L,
                    TimeUnit.SECONDS
            ); // 一定要加一个启动延时，否则容易断开链接。
        }
    }

    /**
     * 一定要使用这个方法进行关闭。建议使用建议使用1000。
     * @param code 代码
     * @param message 信息
     */
    @Override
    public void close(int code, String message) {
        if(this.ExecutorService != null) {
            this.ExecutorService.shutdown(); // 关闭心跳。
        }
        super.close(code, message);
    }

    @Override
    public void close() {
        if(this.ExecutorService != null) {
            this.ExecutorService.shutdown(); // 关闭心跳。
        }
        super.close();
    }

    @Override
    public void closeBlocking() throws InterruptedException {
        if(this.ExecutorService != null) {
            this.ExecutorService.shutdown(); // 关闭心跳。
        }
        super.closeBlocking();
    }

    @Override
    public void closeConnection(int code, String message) {
        if(this.ExecutorService != null) {
            this.ExecutorService.shutdown(); // 关闭心跳。
        }
        super.closeConnection(code, message);
    }

    /**
     * 构造器。
     */
    public static class Builder {
        public URI Uri;
        public String Json;

        public OnError OnE;
        public OnClose OnC;
        public OnOpen OnO;
        public OnMessageByte OmsB;
        public OnMessageString OmsS;

        /**
         * 设置房间的链接ID。
         * @param room_id_url 直播间的链接ID。
         * @return this
         * @throws HttpApiException 如果HTTP协议API范文出现问题。
         * @throws URISyntaxException 如果获取的弹幕服务器的链接格式有问题。
         */
        public Builder setRoomUrlID(long room_id_url) throws HttpApiException, URISyntaxException {
            var info = getHandshakeInfo(room_id_url);
            this.Uri = new URI(info.ServiceHost[0]);
            this.Json = info.HandshakeJson;
            return this;
        }

        /**
         * 构造一个新的wss客户端，注意这个方法会进行第一次握手的处理。这意味着，调用这个方法后，你只能进行关闭操作。
         * @return wss客户端
         * @throws InterruptedException 如果出现链接中断。
         */
        public DanmuWSSClient build() throws InterruptedException {
            var ser = new DanmuWSSClient(this,this.Uri);
            byte[] json = this.Json.getBytes(StandardCharsets.UTF_8);

            byte[] frame = DanmuCoder.frameEncoder(
                    16 + json.length, (short) 16, (short) 1, 7, 1, json
            );
            ser.start(frame, true);
            return ser;
        }

        /**
         * 如果接收到字节消息如何处理。
         * @param omsB 字节消息处理的句柄。
         * @return this
         */
        public Builder setOnMessageByte(OnMessageByte omsB) {
            OmsB = omsB;
            return this;
        }


        public Builder setOnMessageString(OnMessageString omsS) {
            OmsS = omsS;
            return this;
        }

        public Builder setOnClose(OnClose onC) {
            OnC = onC;
            return this;
        }

        public Builder setOnError(OnError onE) {
            OnE = onE;
            return this;
        }

        public Builder setOnOpen(OnOpen onO) {
            OnO = onO;
            return this;
        }
    }
}
